import java.io.Console;
import java.lang.foreign.Arena;
import java.lang.foreign.FunctionDescriptor;
import java.lang.foreign.Linker;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.SymbolLookup;
import java.nio.charset.StandardCharsets;
import util.UnsafeUtil;
import static java.lang.foreign.ValueLayout.*;

public class WinCon {
	public static final int STD_INPUT_HANDLE = -10;
	public static final int STD_OUTPUT_HANDLE = -11;
	public static final int STD_ERROR_HANDLE = -12;
	public static final int FOREGROUND_BLUE = 0x0001;
	public static final int FOREGROUND_GREEN = 0x0002;
	public static final int FOREGROUND_RED = 0x0004;
	public static final int FOREGROUND_INTENSITY = 0x0008;
	public static final int BACKGROUND_BLUE = 0x0010;
	public static final int BACKGROUND_GREEN = 0x0020;
	public static final int BACKGROUND_RED = 0x0040;
	public static final int BACKGROUND_INTENSITY = 0x0080;
/*
	public static final MethodHandle getDeclaredFields0MH;

	static {
		try {
			var getDeclaredFields0 = Class.class.getDeclaredMethod("getDeclaredFields0", boolean.class);
			unsafe.putBoolean(getDeclaredFields0, OVERRIDE_OFFSET, true);
			getDeclaredFields0MH = MethodHandles.lookup().unreflect(getDeclaredFields0);
			var m = WinCon.class.getModule();
			var fs = (Field[])getDeclaredFields0MH.invokeExact(m.getClass(), false);
			for (var f : fs) {
				if ("ALL_UNNAMED_MODULE".equals(f.getName())) {
					unsafe.putBoolean(f, OVERRIDE_OFFSET, true);
					var allUnnamedModule = f.get(null);
					for (var f2 : fs) {
						if ("enableNativeAccess".equals(f2.getName())) {
							// suppress WARNING: Use --enable-native-access=%s to avoid a warning for callers in this module
							unsafe.putBoolean(f2, OVERRIDE_OFFSET, true);
							f2.set(allUnnamedModule, true);
							break;
						}
					}
					break;
				}
			}
		} catch (Throwable e) {
			throw new AssertionError(e);
		}
	}
*/

	public static void main(String[] args) throws Exception {
		var c = Class.forName("jdk.internal.org.jline.terminal.impl.ffm.Kernel32");
		var m = UnsafeUtil.getMethod(c, "GetStdHandle", int.class);
		var hout = m.invoke(null, STD_OUTPUT_HANDLE);
		m = UnsafeUtil.getMethod(c, "SetConsoleTextAttribute", MemorySegment.class, short.class);
		m.invoke(null, hout, (short)(FOREGROUND_GREEN | FOREGROUND_INTENSITY));
		System.out.println("Hello");
		m.invoke(null, hout, (short)(FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED));
		System.out.println("end");
	}

	@SuppressWarnings("OptionalGetWithoutIsPresent")
	public static void main1(String[] args) throws Throwable {
		var libKernel32 = SymbolLookup.libraryLookup("kernel32.dll", Arena.global());
		var linker = Linker.nativeLinker();

		// DWORD GetLastError();
		var mhGetLastError = linker.downcallHandle(libKernel32.find("GetLastError").get(),
				FunctionDescriptor.of(JAVA_INT));

		// BOOL WINAPI AllocConsole(void);
		var mhAllocConsole = linker.downcallHandle(libKernel32.find("AllocConsole").get(),
				FunctionDescriptor.of(JAVA_INT));

		// HANDLE WINAPI GetStdHandle(DWORD nStdHandle);
		var mhGetStdHandle = linker.downcallHandle(libKernel32.find("GetStdHandle").get(),
				FunctionDescriptor.of(JAVA_LONG, JAVA_INT));

		// BOOL WINAPI WriteConsole(HANDLE hConsoleOutput, const VOID* lpBuffer, DWORD nNumberOfCharsToWrite,
		//                          LPDWORD lpNumberOfCharsWritten, LPVOID lpReserved);
		var mhWriteConsoleA = linker.downcallHandle(libKernel32.find("WriteConsoleA").get(),
				FunctionDescriptor.of(JAVA_INT, JAVA_LONG, ADDRESS, JAVA_INT, ADDRESS, ADDRESS));

		// BOOL WINAPI SetConsoleTextAttribute(HANDLE hConsoleOutput, WORD wAttributes);
		var mhSetConsoleTextAttribute = linker.downcallHandle(libKernel32.find("SetConsoleTextAttribute").get(),
				FunctionDescriptor.of(JAVA_INT, JAVA_LONG, JAVA_SHORT));

		int r = (int)mhAllocConsole.invoke();
		int e = (int)mhGetLastError.invoke();
		System.out.println("AllocConsole = " + r);
		System.out.println("GetLastError = " + e);

		var hout = (long)mhGetStdHandle.invoke(STD_OUTPUT_HANDLE);
		System.out.println("GetStdHandle(STDOUT) = " + hout);

		r = (int)mhSetConsoleTextAttribute.invoke(hout, (short)(FOREGROUND_GREEN | FOREGROUND_INTENSITY));
		System.out.println("SetConsoleTextAttribute = " + r);

		var str = Arena.global().allocateFrom("Hello\n", StandardCharsets.UTF_8);
		r = (int)mhWriteConsoleA.invoke(hout, str, (int)str.byteSize() - 1, MemorySegment.NULL, MemorySegment.NULL);
		e = (int)mhGetLastError.invoke();
		System.out.println("WriteConsole = " + r);
		System.out.println("GetLastError = " + e);
		mhSetConsoleTextAttribute.invoke(hout, (short)(FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED));

		// Thread.sleep(999_999_999);
	}

	public static void main2(String[] args) throws Exception {
		System.setProperty("jdk.console", "jdk.internal.le");

		var console = (Console)UnsafeUtil.getField(Console.class, "cons").get(null);
		System.out.println(console.getClass().getName());

		var console2 = UnsafeUtil.getField(console.getClass(), "delegate").get(console);
		System.out.println(console2.getClass().getName());

		var terminal = UnsafeUtil.getField(console2.getClass(), "terminal").get(console2);
		System.out.println(terminal.getClass().getName());

		var writer = UnsafeUtil.getField(terminal.getClass().getSuperclass(), "writer").get(terminal);
		System.out.println(writer.getClass().getName());

		System.out.println(UnsafeUtil.getField(writer.getClass(), "out").get(writer).getClass().getName());
	}
}
